﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;

namespace Microsoft.Reputation.Tests.FrontEndCommon
{
    public partial class UrfReputationObject
    {
        private static readonly Regex UrfLine = new Regex(
            @"^(?<url>\S+)?\W*(?<hash>\S+)\W*(?<bloom>\S*)\W*(?<reputation>\S*)",
            RegexOptions.Compiled);

        public static UrfReputationObject ReadToReputationObject(string raw)
        {
            return ReadToReputationObject(raw, MrsDeliveryServiceHelper.DefaultObjectVersion);
        }

        public static UrfReputationObject ReadToReputationObject(string raw, long version)
        {
            return ReadToReputationObject(raw, 4, MrsDeliveryServiceHelper.ExpirationTimes[7], MrsDeliveryServiceHelper.ExpirationTimes[8], version);
        }

        public static UrfReputationObject ReadToReputationObject(string raw, uint type, uint serverExp, uint clientExp, long version)
        {
            if (String.IsNullOrEmpty(raw))
            {
                return null;
            }
            Match lineMatch = UrfLine.Match(raw);
            if (!lineMatch.Success)
            {
                return null;
            }
            string url = lineMatch.Groups["url"].Value;
            string hash = lineMatch.Groups["hash"].Value;
            string bloom = lineMatch.Groups["bloom"].Value;
            string reputation = lineMatch.Groups["reputation"].Value;

            bool tempFlags;

            return new UrfReputationObject()
            {
                Type = (byte)type,
                Expiration = 78, //Index of 7 gives the Expiration Min = 1440, Index of 8 gives the Expiration Min = 10080
                Version = version, //Least possible version value being used for test data
                Url = url,
                Hash = hash.HexToBytes(),
                Bloom = bloom.HexToBytes(),
                Flags = (BloomFlags)UrfReputationObject.CalculateFlags(out tempFlags, bloom.HexToBytes()),
                Reputation = reputation.Split(',').Select(r => ReputationCategory.ReadToCategory(r)).Where(c => c != null).ToArray(),
                ServerExpirationMinutes = serverExp,
                ClientExpirationMinutes = clientExp
            };
        }

        private static IEnumerable<byte> GenerateBloom(bool fullBloom)
        {
            yield return 0x08;
            if (fullBloom)
            {
                for (int i = 0; i < 32; i++)
                {
                    yield return 0xFF;
                }
            }
            else
            {
                
                for (int i = 0; i < 32; i++)
                {
                    yield return 0x00;
                }
            }
        }

        private static IEnumerable<object> UnpackInternal(byte[] packedReputation)
        {
            int index = 0;
            yield return packedReputation[index];
            index += 1;
            yield return packedReputation.Skip(index).Take(32).ToArray();
            index += 32;
            yield return BitConverter.ToInt64(packedReputation.Skip(index).Take(8).Reverse().ToArray(), 0);
            index += 8;

            byte categoryCount = packedReputation.Skip(index).First();
            index += 1;
            byte bloomFlags = packedReputation.Skip(index).First();
            yield return (BloomFlags)bloomFlags;
            index += 1;
            yield return packedReputation.Skip(index).First(); // expiration
            index += 1;

            List<ReputationCategory> categories = new List<ReputationCategory>();
            for (int i = 0; i < categoryCount; i++)
            {
                ReputationCategory reputationCategory = ReputationCategory.Unpack(packedReputation, index);
                if (reputationCategory != null)
                {
                    categories.Add(reputationCategory);
                }
                index += sizeof(int);
            }
            yield return categories.ToArray();

            bool hasBloom = ((BloomFlags)bloomFlags & BloomFlags.ValidBloom) == BloomFlags.ValidBloom;
            if (((BloomFlags)bloomFlags & BloomFlags.FullBloom) == BloomFlags.FullBloom)
            {
                yield return GenerateBloom(true).ToArray();
            }
            else if (hasBloom)
            {
                yield return packedReputation.Skip(index).Take(33).ToArray();
            }
            else if (((BloomFlags)bloomFlags & BloomFlags.NoBloom) == BloomFlags.NoBloom)
            {
                yield return GenerateBloom(false).ToArray();
            }
            else
            {
                yield return new byte[0];
            }
        }

        public static UrfReputationObject Unpack(byte[] packedReputation)
        {
            IEnumerator<object> unpackedInternally = UnpackInternal(packedReputation).GetEnumerator();
            UrfReputationObject returnValue = new UrfReputationObject();

            unpackedInternally.MoveNext();
            returnValue.Type = (byte)unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Hash = (byte[])unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Version = (Int64)unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Flags = (BloomFlags)unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Expiration = (byte)unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Reputation = (ReputationCategory[])unpackedInternally.Current;
            unpackedInternally.MoveNext();
            returnValue.Bloom = (byte[])unpackedInternally.Current;

            return returnValue;
        }

        public static UrfReputationObject Unpack(byte[] packedReputation, uint[] ExpirationArray)
        {
            UrfReputationObject returnValue = UrfReputationObject.Unpack(packedReputation);

            int serverIndex = (int)(returnValue.Expiration >> 4);
            int clientIndex = (int)(returnValue.Expiration & 0x0F);

            returnValue.ServerExpirationMinutes = ExpirationArray[serverIndex];
            returnValue.ClientExpirationMinutes = ExpirationArray[clientIndex];

            return returnValue;
        }
    }
}
